package jp.reflexworks.gae.util;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import jp.sourceforge.reflex.util.FieldMapper;
import jp.sourceforge.reflex.IResourceMapper;
import jp.sourceforge.reflex.core.ResourceMapper;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Text;

public class EntityConverter {

	Logger logger = Logger.getLogger(this.getClass().getName());

	/**
	 * HashMap<Key, Entity>オブジェクトを、List<targetClass>オブジェクトに変換します。（無条件）
	 * @param targetClass 格納先のクラス
	 * @param source データ元
	 */
	public List convert(Class targetClass, Map<Key, Entity> source) {
		return convert(targetClass, source, null);
	}

	/**
	 * HashMap<Key, Entity>オブジェクトを、List<targetClass>オブジェクトに変換します。(条件付）
	 * @param targetClass 格納先のクラス
	 * @param source データ元
	 * @param condition 格納条件
	 */
	public List convert(Class targetClass, Map<Key, Entity> source, Condition condition) {
		List list = new ArrayList<Object>();
		if (source != null && !source.isEmpty()) {
			for (Object key : source.keySet()) {
				Entity entity = source.get(key);
				Object obj = convert(targetClass,entity);
				if (condition != null) {
					if (condition.doCheck(obj)) {
						list.add(obj);
					}
				} else {
					list.add(obj);
				}
			}
		}

		return list;
	}

	/**
	 * HashMap<Key, Entity>オブジェクトを、List<targetClass>オブジェクトに変換します。（無条件）
	 * @param targetClass 格納先のクラス
	 * @param source データ元
	 */
	public List convert(Class targetClass, Iterable<Entity> source) {
		return convert(targetClass, source, null, null);
	}

	/**
	 * HashMap<Key, Entity>オブジェクトを、List<targetClass>オブジェクトに変換します。（SORT）
	 * @param targetClass 格納先のクラス
	 * @param source データ元
	 * @param comparator ソート条件
	 */
	public List convert(Class targetClass, Iterable<Entity> source, Comparator comparator) {
		return convert(targetClass, source, comparator, null);
	}

	/**
	 * HashMap<Key, Entity>オブジェクトを、List<targetClass>オブジェクトに変換します。（SORT）
	 * @param targetClass 格納先のクラス
	 * @param source データ元
	 * @param condition 格納条件
	 */
	public List convert(Class targetClass, Iterable<Entity> source, Condition condition) {
		return convert(targetClass, source, null, condition);
	}

	/**
	 * Iterable<Entity>オブジェクトを、List<targetClass>オブジェクトに変換します。(SORT,条件付）
	 * @param targetClass 格納先のクラス
	 * @param source データ元
	 * @param comparator ソート条件
	 * @param condition 格納条件
	 */
	public List convert(Class targetClass, Iterable<Entity> source, Comparator comparator, Condition condition) {
		List list = new ArrayList<Object>();
		if (source != null) {
			for (Entity entity : source) {
				Object obj = convert(targetClass, entity);
				if (condition != null) {
					if (condition.doCheck(obj)) {
						list.add(obj);
					}
				} else {
					list.add(obj);
				}
			}
		}

		// ソート
		if (comparator != null) {
			Collections.sort(list, comparator);
		}
		return list;
	}


	/**
	 * Entityオブジェクトを、targetClass型のオブジェクトに変換します。
	 * @param targetClass 格納先のクラス
	 * @param entity データ元１件
	 */
	public Object convert(Class targetClass, Entity entity) {
		Object obj = null;
		try {
			obj = targetClass.newInstance();

			Field[] fields = targetClass.getDeclaredFields();

			for (Field fld : fields) {
				String fieldName = fld.getName();
				String propName = getPropertyName(fieldName);

				if (entity.hasProperty(propName)) {
					String setter = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);

					try {
						Object propvalue = entity.getProperty(propName);
						if (propvalue != null) {
							this.setValue(obj, fld.getType(), propvalue, setter);
						}

					} catch (IllegalArgumentException e) {
						e.printStackTrace();
					}

				} else if (fld.getType().equals(Key.class)) {
					String setter = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);

					try {
						Object keyvalue = entity.getKey();
						if (keyvalue != null) {
							this.setValue(obj, fld.getType(), keyvalue, setter);
						}

					} catch (IllegalArgumentException e) {
						e.printStackTrace();
					}
				}
			}

		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		}

		return obj;
	}

	public void setValue(Object source, Class type, Object value, String setter) {

		Method method = null;
		try {
			method = source.getClass().getMethod(setter, type);
			method.invoke(source, value);

		} catch (SecurityException e2) {
			// Do Nothing
		} catch (NoSuchMethodException e2) {
			// Do Nothing
		} catch (IllegalArgumentException e) {

			try {
				if (value instanceof Long && (type.equals(int.class) || type.equals(Integer.class))) {
					// JDO PropertyではintもLongとして扱っているので変換する
					method.invoke(source, (int)(long)((Long)value));

				} else if (value instanceof Double && (type.equals(float.class) || type.equals(Float.class))) {
					// JDO PropertyではfloatもDoubleとして扱っているので変換する
					method.invoke(source, (float)(double)((Double)value));
				}

			} catch (IllegalArgumentException e1) {
				e1.printStackTrace();
			} catch (IllegalAccessException e1) {
				e1.printStackTrace();
			} catch (InvocationTargetException e1) {
				e1.printStackTrace();
			}

		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}

	}

	public Object getValue(Object source, String getter) {
		Method method = null;
		Object ret = null;

		try {
			method = source.getClass().getMethod(getter);
			ret = method.invoke(source);

		} catch (SecurityException e2) {
			// Do Nothing
		} catch (NoSuchMethodException e2) {
			// Do Nothing
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}

		return ret;
	}

	/**
	 * ObjectからEntityクラスを生成する
	 * @param obj (kind)
	 * @param keyName キーの値
	 * @param parent 親キー
	 */
	public Entity generate(Object obj, String keyName, Key parent) {
		return generate(null, obj, keyName, parent);
	}

	/**
	 * ObjectからEntityクラスを生成する
	 * @param kind kind名
	 * @param obj データ
	 * @param keyName キーの値
	 * @param parent 親キー
	 */
	public Entity generate(String kind, Object obj, String keyName, Key parent) {
		Class targetClass = obj.getClass();
		if (kind == null) {
			kind = targetClass.getSimpleName();
		}
		Entity entity = new Entity(kind, keyName, parent);

		// index
		Field[] fields = targetClass.getDeclaredFields();

		FieldMapper fieldMapper = new FieldMapper();

		for (Field fld : fields) {
			if (fieldMapper.isPersistent(fld) && !fieldMapper.isPrimaryKey(fld)) {
				String fieldName = fld.getName();
				String propName = getPropertyName(fieldName);

				String getter = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
				try {
					Object propvalue = this.getValue(obj, getter);
					if (propvalue != null) {
						entity.setProperty(propName, propvalue);
					}

				} catch (IllegalArgumentException e) {
					logger.log(Level.WARNING, e.getMessage(), e);
				}
			}
		}

		return entity;
	}

	/**
	 * ObjectからEntityクラスを生成する
	 * @param obj (kind)
	 * @param keyName キーの値
	 * @param parent 親キー
	 * @param indexList
	 */
	public Entity generate(Object obj, String keyName, Key parent, String[] indexList) {

		Class targetClass = obj.getClass();
		Entity entity = new Entity(targetClass.getSimpleName(), keyName, parent);

		// index
		Field[] fields = targetClass.getDeclaredFields();

		FieldMapper fieldMapper = new FieldMapper();

		for (Field fld : fields) {
			if (fieldMapper.isPersistent(fld) && !fieldMapper.isPrimaryKey(fld)) {
				String fieldName = fld.getName();
				String propName = getPropertyName(fieldName);

				if (isIndex(indexList, fieldName)) {
					String getter = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
					try {
						Object propvalue = this.getValue(obj, getter);
						if (propvalue != null) {
							entity.setProperty(propName, propvalue);
						}

					} catch (IllegalArgumentException e) {
						logger.log(Level.WARNING, e.getMessage(), e);
					}
				}
			}
		}

		if (parent != null) {	// 子のみ
			// その他は'contents'プロパティにJSONで登録する
			IResourceMapper rxmapper = new ResourceMapper(targetClass.getPackage().toString());
			String contents = rxmapper.toJSON(obj);

			entity.setProperty("contents", new Text(contents));
		}

		return entity;
	}

	private boolean isIndex(String[] indexList,String fieldName) {
		if (fieldName != null) {
			for (String index : indexList) {
				if (fieldName.equals(index)) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Iterable<Entity>オブジェクトを、List<targetClass>オブジェクトに変換します。(SORT,条件付）
	 * @param targetClass 格納先のクラス
	 * @param source データ元
	 * @param comparator ソート条件
	 * @param condition 格納条件
	 */
	public List convertContents(Class targetClass, Iterable<Entity> source, Comparator comparator, Condition condition) {
		List list = new ArrayList<Object>();
		if (source != null) {
			for (Entity entity : source) {
				Object obj = convert(targetClass, entity);
				if (condition != null) {
					if (condition.doCheck(obj)) {
						list.add(obj);
					}
				} else {
					list.add(obj);
				}
			}
		}

		// ソート
		if (comparator != null) {
			Collections.sort(list, comparator);
		}
		return list;
	}

	/**
	 * Entityオブジェクトを、targetClass型のオブジェクトに変換します。
	 * @param targetClass 格納先のクラス
	 * @param entity データ元１件
	 */
	public Object convertContents(Class targetClass, Entity entity) {
		String contents = ((Text)entity.getProperty("contents")).getValue();

		Object obj = null;

		String packageStr = targetClass.getPackage().toString();
		if (packageStr != null && packageStr.startsWith("package ")) {
			packageStr = packageStr.substring(8);
		}

		IResourceMapper rxmapper = new ResourceMapper(packageStr);
		try {
			obj = rxmapper.fromJSON(contents);
		} catch (org.json.JSONException e) {
			e.printStackTrace();
		}

		return obj;
	}

	/**
	 * 項目名を、Entityのプロパティ名に変換します。
	 * ・"$"を"24"に変換
	 * ・先頭の"_"を""に変換
	 */
	public String getPropertyName(String fieldName) {
		if (fieldName == null) {
			return null;
		}

		String propName = new String(fieldName);
		if (propName.length() > 0 && propName.startsWith("_")) {
			propName = propName.substring(1);
		}
		propName = propName.replace("$", "24");
		return propName;
	}

}
